﻿using Hangfire;
using Hangfire.HttpJob.CSRedis;
using Hangfire.HttpJob.CSRedis.Middleware;
using Hangfire.Redis;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Microsoft.Extensions.DependencyInjection
{
    /// <summary>
    /// redis hangfire扩展服务
    /// </summary>
    public static class HangfireRedisExtension
    {
        internal static Dictionary<Type, Type> Dic = new Dictionary<Type, Type>();

        /// <summary>
        /// hangfire redis存储服务
        /// </summary>
        public static IServiceCollection AddHangfireRedis(this IServiceCollection services, Action<HangfireRedisConfig> action = null)
        {
            var hangfireConfig = new HangfireRedisConfig();
            action?.Invoke(hangfireConfig);
            return services.AddHangfireRedis(hangfireConfig);
        }

        /// <summary>
        /// hangfire redis存储服务
        /// </summary>
        public static IServiceCollection AddHangfireRedis(this IServiceCollection services, IConfiguration configuration)
        {
            var hangfireConf = configuration.Get<HangfireRedisConfig>();
            return services.AddHangfireRedis(hangfireConf);
        }

        /// <summary>
        /// hangfire redis存储服务
        /// </summary>
        public static IServiceCollection AddHangfireRedis(this IServiceCollection services, HangfireRedisConfig hangfireRedisConfig)
        {
            var hangfireConfig = hangfireRedisConfig ?? new HangfireRedisConfig();
            var redisOption = new RedisStorageOptions
            {
                Prefix = hangfireConfig.Prefix,
                FetchTimeout = TimeSpan.FromMinutes(hangfireConfig.FetchTimeout),
                ExpiryCheckInterval = TimeSpan.FromMinutes(hangfireConfig.ExpiryCheckInterval)
            };
            GlobalConfiguration.Configuration.UseRedisStorage(hangfireConfig.RedisConnection, redisOption);
            JobStorage.Current.JobExpirationTimeout = TimeSpan.FromHours(hangfireConfig.SuccedExpireTime);
            GlobalJobFilters.Filters.Add(new DisableMultipleQueueAttribute());
            GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute() { Attempts = hangfireConfig.Retry, DelaysInSeconds = new int[1] { 5 } });
            services.AddSingleton(hangfireConfig);
            services.AddHttpClient();
            services.AddSingleton<HttpJobInvoker>();
            services.AddHangfireServer((sp, bjso) =>
            {
                bjso.Queues = hangfireConfig.QueueList.ToArray();
                bjso.SchedulePollingInterval = TimeSpan.FromSeconds(hangfireConfig.SchedulePollingInterval);
                bjso.ServerTimeout = TimeSpan.FromMinutes(hangfireConfig.ServerTimeout);
                bjso.ServerCheckInterval = TimeSpan.FromMinutes(hangfireConfig.ServerCheckInterval);
            });
            services.AddHangfire((sp, globalConfig) =>
            {
            });
            services.RegisterJob();
            services.AddHostedService<JobHostedService>();

            return services;
        }

        /// <summary>
        /// hangfire redis dashboard and callback url
        /// </summary>
        public static IApplicationBuilder UseHanfgireRedis(this IApplicationBuilder app, string callbackPath = "/hangfire_redis_callback", string dashboardPath = "/hangfire_job", DashboardOptions options = null)
        {
            callbackPath = string.IsNullOrWhiteSpace(callbackPath) ? "/hangfire_redis_callback" : callbackPath;
            dashboardPath = string.IsNullOrWhiteSpace(dashboardPath) ? "/hangfire_job" : dashboardPath;
            options ??= new DashboardOptions();
            options.DisplayStorageConnectionString = false;
            options.AppPath = null;
            options.DashboardTitle = "作业管理器";
            options.IgnoreAntiforgeryToken = true;
            options.IsReadOnlyFunc = context => false;
            app.UseHangfireDashboard(dashboardPath, options);
            app.MapWhen(httpContext => httpContext.Request.Path.Value.StartsWith(callbackPath),
                builder => builder.UseMiddleware<CallBackMiddleware>());

            return app;
        }

        #region private methods
        /// <summary>
        /// 注册实现了IJob的非泛型类
        /// </summary>
        private static void RegisterJob(this IServiceCollection services)
        {
            var jobTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(m => m.GetTypes())
                .Where(m => m.IsPublic && m.IsClass && !m.IsAbstract && !m.IsGenericType && typeof(IJob).IsAssignableFrom(m));
            foreach (var type in jobTypes)
            {
                services.AddScoped(typeof(IJob), type);
                Dic.TryAdd(type, typeof(IJob));
            }
        }
        #endregion
    }
}
